package com.lyc.lojbackendjudgeservice.judge;

import cn.hutool.json.JSONUtil;
import com.lyc.lojbackendcommon.common.ErrorCode;
import com.lyc.lojbackendcommon.exception.BusinessException;
import com.lyc.lojbackendjudgeservice.judge.codesandbox.CodeSandbox;
import com.lyc.lojbackendjudgeservice.judge.codesandbox.CodeSandboxFactory;
import com.lyc.lojbackendjudgeservice.judge.codesandbox.CodeSandboxProxy;
import com.lyc.lojbackendjudgeservice.judge.strategy.JudgeContext;
import com.lyc.lojbackendmodel.model.codesandbox.ExecuteCodeRequest;
import com.lyc.lojbackendmodel.model.codesandbox.ExecuteCodeResponse;
import com.lyc.lojbackendmodel.model.codesandbox.JudgeInfo;
import com.lyc.lojbackendmodel.model.dto.question.JudgeCase;
import com.lyc.lojbackendmodel.model.dto.questionsubmit.TestCase;
import com.lyc.lojbackendmodel.model.entity.Question;
import com.lyc.lojbackendmodel.model.entity.QuestionSubmit;
import com.lyc.lojbackendmodel.model.enums.CodeSandboxEnum;
import com.lyc.lojbackendmodel.model.enums.QuestionSubmitResultStatusEnum;
import com.lyc.lojbackendmodel.model.enums.QuestionSubmitStatusEnum;
import com.lyc.lojbackendserviceclient.service.QuestionFeignClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
public class JudgeServiceImpl implements JudgeService {

    @Value("${codesandbox.type:example}")
    private CodeSandboxEnum type;

    @Resource
    private QuestionFeignClient questionFeignClient;

    @Resource
    private JudgeManager judgeManager;

    /**
     * 执行判题，在这里调用代码沙箱
     * @param questionSubmitId
     * @return
     */

    /**
     * 执行判题，添加测试用例，
     * @param questionSubmitId
     * @return
     */
    public QuestionSubmit doJudge(long questionSubmitId) {
        QuestionSubmit questionSubmit = questionFeignClient.getQuestionSubmitById(questionSubmitId);
        if (questionSubmit == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "提交信息不存在");
        }
        Long questionId = questionSubmit.getQuestionId();
        Question question = questionFeignClient.getQuestionById(questionId);
        if (question == null) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "题目不存在");
        }
        //如果不为等待状态
        if (!questionSubmit.getStatus().equals(QuestionSubmitStatusEnum.WAITING.getValue())) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "题目正在判题中");
        }
        //更改判题（题目提交）的状态为判题中，防止重复执行，也能让用户即时看到状态
        QuestionSubmit questionSubmitUpdate = new QuestionSubmit();
        questionSubmitUpdate.setId(questionSubmitId);
        questionSubmitUpdate.setStatus(QuestionSubmitStatusEnum.RUNNING.getValue());
        boolean update = questionFeignClient.updateQuestionSubmitById(questionSubmitUpdate);
        if (!update) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "题目状态更新错误");
        }
        //获取提交的语言和代码,题目的测试用例的输入和输出
        String language = questionSubmit.getLanguage();
        String code = questionSubmit.getCode();
        // 获取题目判题用例
        String judgeCaseStr = question.getJudgeCase();
        List<JudgeCase> judgeCases = JSONUtil.toList(judgeCaseStr, JudgeCase.class);
        Integer isManual = questionSubmit.getIsManual();
        List<String> inputList = new ArrayList<>();
        // 根据 isManual 的值设置对应的 inputList
        if(isManual == 1) {
            // 如果是人工测评，点击测试按钮，inputList 应该为用户输入的测试用例。
            String testCaseStr = questionSubmit.getTestCase();
            inputList.add(testCaseStr);
        }else{
            // 如果不是人工测评，点击提交按钮，inputList 为题目本身的判题用例。
            inputList = judgeCases.stream().map(JudgeCase::getInput).collect(Collectors.toList());
        }
        
        // 调用代码沙箱，执行代码，获取执行结果
        CodeSandbox codeSandbox = CodeSandboxFactory.newInstance(type);
        codeSandbox = new CodeSandboxProxy(codeSandbox);
        ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder()
                .code(code)
                .language(language)
                .inputList(inputList)
                .build();
        ExecuteCodeResponse executeCodeResponse = codeSandbox.executeCode(executeCodeRequest);

        //根据沙箱的执行结果，设置题目的判题状态和信息
        JudgeContext judgeContext = new JudgeContext();
        judgeContext.setJudgeInfo(executeCodeResponse.getJudgeInfo());
        judgeContext.setInputList(inputList);
        judgeContext.setOutputList(executeCodeResponse.getOutputList());
        judgeContext.setJudgeCases(judgeCases);
        judgeContext.setQuestion(question);
        judgeContext.setQuestionSubmit(questionSubmit);
        // 设置是否是用户测试
        judgeContext.setIsManual(isManual);
        // 设置判题状态，如果 1->编译&运行成功，2->编译错误，3->编译成功但是运行错误
        int status = executeCodeResponse.getStatus();
        judgeContext.setStatus(status);
        // 设置代码沙箱返回的信息
        judgeContext.setMessage(executeCodeResponse.getMessage());
        String errorMessage = executeCodeResponse.getErrorMessage();
        String mainContent = errorMessage;
        if(errorMessage != null) {
            // 去除空格和回车
            errorMessage = errorMessage.replaceAll("\\s", "");
            Pattern pattern = Pattern.compile("/home/linyc/loj-code-sandbox/tempCode/[^/]+/");
            Matcher matcher = pattern.matcher(errorMessage);
            StringBuffer result = new StringBuffer();
            while (matcher.find()) {
                matcher.appendReplacement(result, "");
            }
            matcher.appendTail(result);
            mainContent = result.toString();
            judgeContext.setErrorMessage(mainContent);
        }
        // 以上是预处理、从代码沙箱中获取返回的输出结果，并放入到判题上下文中，以便判题。
        //通过Manager实现判题逻辑
        // 用户测试用例需要判题吗？可以通过开发一个测试判题策略，也要判断是否超时和溢出
        JudgeInfo judgeInfo = judgeManager.doJudge(judgeContext);

        // 还是在这里实现通过数加1
        Integer resStatus = judgeInfo.getStatus();
        // 如果只是测试用例，通过数不加1
        if (isManual == 0 && resStatus == 1) {
            synchronized (question) {
                questionFeignClient.updateQuestionAcceptedNumById(questionId);
            }
        }
        // 获取判题结果
        //修改数据库中的判题结果
        questionSubmitUpdate = new QuestionSubmit();
        questionSubmitUpdate.setId(questionSubmitId);
        if(status == 1) {
            // 只有编译和执行都成功，判题状态才成功
            questionSubmitUpdate.setStatus(QuestionSubmitStatusEnum.SUCCEED.getValue());
        }else{
            questionSubmitUpdate.setStatus(QuestionSubmitStatusEnum.FAILED.getValue());
        }
        questionSubmitUpdate.setJudgeInfo(JSONUtil.toJsonStr(judgeInfo));
        questionSubmitUpdate.setDetailError(mainContent);
        if(resStatus.equals(QuestionSubmitResultStatusEnum.SUCCEED.getValue())){
            questionSubmitUpdate.setResStatus(1);
        }else{
            questionSubmitUpdate.setResStatus(0);
        }
        update = questionFeignClient.updateQuestionSubmitById(questionSubmitUpdate);
        if (!update) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "题目状态更新错误");
        }
        return questionFeignClient.getQuestionSubmitById(questionId);
    }
}
 